import 'package:lib_util/utils/utils.dart';
import 'package:objectbox/objectbox.dart';

import '../../../objectbox.g.dart';
import '../basemodel.dart';
import '../token_model/token_model.dart';
import '../../config.dart';

@Entity()
class TransactionBean {
  int id;
  int? localTokenId;
  bool pending;
  String? txId;
  String? methodId;
  String? blockHash;
  String? height;
  int? transactionTime;
  String? from;
  String? to;
  bool? isFromContract;
  bool? isToContract;
  String? amount;
  String? transactionSymbol;
  String? txFee;
  String? state;
  String? tokenId;
  String? tokenContractAddress;
  String? challengeStatus;
  String? l1OriginHash;

  TransactionBean({
    this.id = 0,
    this.localTokenId = 0,
    this.pending = false,
    this.txId,
    this.methodId,
    this.blockHash,
    this.height,
    this.transactionTime,
    this.from,
    this.to,
    this.isFromContract,
    this.isToContract,
    this.amount,
    this.transactionSymbol,
    this.txFee,
    this.state,
    this.tokenId,
    this.tokenContractAddress,
    this.challengeStatus,
    this.l1OriginHash,
  });

  String getFormatTransactionTime() {
    return Utils.formatDateTime(transactionTime!);
  }

  TransactionBean.fromJson(Map<String, dynamic> json, {int decimal = 18, this.id = 0, this.pending = false}) {
    localTokenId = 0;
    txId = json['tx_hash'];
    methodId = json['methodId'];
    blockHash = json['blockHash'];
    height = json['block_number'].toString();
    try {
      if (json['time'].toString().length >= 13) {
        transactionTime = (json['time'] / 1000);
      } else {
        transactionTime = json['time'];
      }
    } catch (e) {
      transactionTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    }
    from = json['from_address'];
    to = json['to_address'];
    dynamic fromContract = json['isFromContract'];
    isFromContract = fromContract is bool ? fromContract : false;
    dynamic toContract = json['isToContract'];
    isToContract = toContract is bool ? json['isFromContract'] : false;
    amount = Utils.formatStringBalance((BigInt.parse(json['value']) / BigInt.from(10).pow(decimal)).toString(),
        fractionDigits: 18, trimDecimalZero: true);
    state = json['status'].toString();
    transactionSymbol = json['transactionSymbol'];
    txFee = Utils.formatStringBalance((BigInt.parse(json['gsa_fee']) / BigInt.from(10).pow(18)).toString(),
        fractionDigits: 18, trimDecimalZero: true);
    state = json['status'].toString();
    pending = (state == 2);
    tokenId = json['tokenId'];
    tokenContractAddress = json['contract_address'];
    challengeStatus = json['challengeStatus'];
    l1OriginHash = json['l1OriginHash'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = id;
    data['localTokenId'] = localTokenId;
    data['pending'] = pending;
    data['txId'] = txId;
    data['methodId'] = methodId;
    data['blockHash'] = blockHash;
    data['height'] = height;
    data['transactionTime'] = transactionTime;
    data['from'] = from;
    data['to'] = to;
    data['isFromContract'] = isFromContract;
    data['isToContract'] = isToContract;
    data['amount'] = amount;
    data['transactionSymbol'] = transactionSymbol;
    data['txFee'] = txFee;
    data['state'] = state;
    data['tokenId'] = tokenId;
    data['tokenContractAddress'] = tokenContractAddress;
    data['challengeStatus'] = challengeStatus;
    data['l1OriginHash'] = l1OriginHash;
    return data;
  }
}

class TransactionModel extends BaseModel<TransactionBean> {
  TransactionModel(Box<TransactionBean> box) : super(box);

  List<TransactionBean> getTransactionList(Token token) {
    String walletAddress = token.getWalletInfo()!.address.toLowerCase();

    List<TransactionBean> data = box
        .query(TransactionBean_.localTokenId.equals(token.id))
        .order(TransactionBean_.transactionTime, flags: Order.descending) //默认升序
        .build()
        .find();
    //fix transaction 错乱
    List<int> errorIds = [];
    List<TransactionBean> result = [];
    for (var t in data) {
      if (_validTransaction(walletAddress, token, t)) {
        result.add(t);
      } else {
        errorIds.add(t.id);
      }
    }
    if (errorIds.isNotEmpty) {
      box.removeMany(errorIds);
    }
    return result;
  }

  int getPaddingTransactionCount(Token token) {
    return box
        .query(TransactionBean_.localTokenId.equals(token.id) & TransactionBean_.pending.equals(true))
        .build()
        .count();
  }

  List<TransactionBean> getPaddingTransactionList(Token token) {
    return box
        .query(TransactionBean_.localTokenId.equals(token.id) & TransactionBean_.pending.equals(true))
        .order(TransactionBean_.transactionTime, flags: Order.descending)
        .build().find();
  }

  TransactionBean? getFirstPaddingTransaction(Token token) {
    return box
        .query(TransactionBean_.localTokenId.equals(token.id) & TransactionBean_.pending.equals(true))
        .order(TransactionBean_.transactionTime, flags: Order.descending)
        .build()
        .findFirst();
  }

  resetTransactionList(Token token) {
    List<int> ids = box.query(TransactionBean_.localTokenId.equals(token.id)).build().findIds();
    box.removeMany(ids);
  }

  putPendingTransaction(Token token, TransactionBean transaction) {
    transaction.localTokenId = token.id;
    transaction.pending = true;

    box.put(transaction);
  }

  updatePendingTransactionState(TransactionBean transaction) {
    box.put(transaction, mode: PutMode.update);
  }

  updatePendingTransactionState2(TransactionBean transaction) {
    TransactionBean? old = box.query(TransactionBean_.txId.equals(transaction.txId!)).build().findFirst();
    if (old != null) {
      old.height = transaction.height;
      old.state = transaction.state;
      old.pending = false;
      old.txFee = transaction.txFee;
      box.put(old, mode: PutMode.update);
    }
  }

  bool _validTransaction(String lowerWalletAddress, Token token, TransactionBean t) {
    //目标不一样
    if (t.from!.toLowerCase() != lowerWalletAddress && t.to!.toLowerCase() != lowerWalletAddress) {
      return false;
    }

    //合约地址不一样
    String contract = t.tokenContractAddress ?? '';
    if (token.contractType != TokenType.native.name && contract.toLowerCase() != token.contract.toLowerCase()) {
      return false;
    }

    //原生代币必须保持一致
    if (token.contractType == TokenType.native.name &&
        contract.toLowerCase() != lowerWalletAddress &&
        contract.isNotEmpty) {
      return false;
    }

    return true;
  }

  putTransactionListFromServer(Token token, List<TransactionBean> list) {
    String walletAddress = token.getWalletInfo()!.address.toLowerCase();
    List<TransactionBean> needInsertList = [];
    for (var t in list) {
      if (!_validTransaction(walletAddress, token, t)) {
        break;
      }

      t.localTokenId = token.id;

      TransactionBean? cachedTransaction = box
          .query(TransactionBean_.localTokenId.equals(t.localTokenId!) &
              TransactionBean_.txId.equals(t.txId!, caseSensitive: false))
          .build()
          .findFirst();
      if (null == cachedTransaction) {
        needInsertList.add(t);
      } else {
        //非btc链情况下，只有服务端返回交易记录中有这笔交易，那么肯定是已经确认的了的，也就是非pending状态
        if (token.getChain()?.isBtcOrBtcTestnet() ?? false) {
          if (cachedTransaction.pending != t.pending ||
              cachedTransaction.transactionTime != t.transactionTime ||
              cachedTransaction.height != t.height ||
              cachedTransaction.txFee != t.txFee) {
            cachedTransaction.height = t.height;
            cachedTransaction.txFee = t.txFee;
            cachedTransaction.pending = t.pending;
            cachedTransaction.transactionTime = t.transactionTime;
            box.put(cachedTransaction, mode: PutMode.update);
          }
        } else {
          // bool update = false;
          // if (cachedTransaction.txFee != t.txFee) {
          //   cachedTransaction.txFee = t.txFee;
          //   update = true;
          // }

          // if (cachedTransaction.pending) {
          //   cachedTransaction.pending = false;
          //   update = true;
          // }
          if ((cachedTransaction.pending || cachedTransaction.state == '2') && t.state != '2') {
            cachedTransaction.state = t.state;
            cachedTransaction.pending = false;
            cachedTransaction.transactionTime = t.transactionTime;
            box.put(cachedTransaction, mode: PutMode.update);
          } else {
            cachedTransaction.amount = t.amount;
            cachedTransaction.transactionTime = t.transactionTime;
            box.put(cachedTransaction, mode: PutMode.update);
          }
        }
      }
    }
    if (needInsertList.isNotEmpty) {
      box.putMany(needInsertList, mode: PutMode.insert);
    }

    int transactionCount = box.query(TransactionBean_.localTokenId.equals(token.id)).build().count();
    if (transactionCount > Config.maxStoreTransactionCountForSingleToken) {
      List<int> ids = box
          .query(TransactionBean_.localTokenId.equals(token.id))
          .order(TransactionBean_.transactionTime) //默认升序
          .build()
          .findIds();
      List<int> needDeleteIds = ids.sublist(0, transactionCount - Config.maxStoreTransactionCountForSingleToken);
      box.removeMany(needDeleteIds);
    }
  }
}
